Skip to content

release: release/schema-0.30.0+cli-0.27.0 - #295

Draft
nathanacurtis wants to merge 8 commits into
mainfrom
release/schema-0.30.0+cli-0.27.0
Draft

release: release/schema-0.30.0+cli-0.27.0#295
nathanacurtis wants to merge 8 commits into
mainfrom
release/schema-0.30.0+cli-0.27.0

Conversation

@nathanacurtis

Copy link
Copy Markdown
Member

Draft release PR — opened automatically by start-next-release so the in-flight release is visible. Versions, CHANGELOG, build, tests, and publish will be finalized by the release skill.

nathanacurtis and others added 8 commits August 7, 2026 09:44
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfAEBwDThqo1KpCL8GaruH
specs-from-figma moved src/ into packages/specs-from-figma/, so
file:../specs-from-figma now resolves to the workspace root — private,
no main — and the engine fails to import. Dev-time link only; the CLI's
own dependency stays a published version range.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELnRyPVrSVkXwqpPxH4Ci4
…eservation (#296)

* docs(adr): claim ADR 066 in INDEX

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfAEBwDThqo1KpCL8GaruH

* docs(adr): 066 lossless key formatting (DRAFT)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfAEBwDThqo1KpCL8GaruH

* docs(adr): 066 rename to com.figma.name, narrow figmaKeys, fix safe char set

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfAEBwDThqo1KpCL8GaruH

* feat(schema)!: lossless key formatting — format.figmaKeys, safe key grammar, com.figma.name

Implements ADR-066.

BREAKING: FigmaAnatomyElementExtension.originalName renamed to name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfAEBwDThqo1KpCL8GaruH

* docs(066): NONE default, pass-through rule, coverage inventory

Tune-up of ADR-066 following review:

- Add NONE to format.figmaKeys and make it the default, so the safe key
  grammar, the divergence extension, and reversal are opt-in. NONE preserves
  today's behavior but not today's bytes — the originalName -> name rename
  applies unconditionally.
- Add Decision 5: names already written in the destination format are passed
  through unformatted and record com.figma.name. Predicate is an idempotence
  test (formatKey(name, keys) === name) rather than six per-format grammars,
  excluding SAFE.
- Correct the safe key grammar: a word is all letters or all digits, a name is
  never digit-initial. Makes "Badge count 2" safe and "Badge count2" unsafe.
- State the word-splitting rule (spaces, case transitions, letter<->digit
  transitions) that makes reversal deterministic.
- Add a coverage inventory of every keyed space in the schema, with the
  normative requirement that producers recurse into Composition.anatomy and
  SlotContent.anatomy.
- Add an audit of key application surfaces in figma-from-specs, including the
  variant option values gap.
- Suggest a follow-on `specs analyze keys` report.
- Fix Semver: DEFAULT_CONFIG supplies NONE, and the required ResolvedConfig
  field is MAJOR-class.
- Qualify Consequences that overclaimed against the NONE default and the
  wrapper-collapse trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* feat(066)!: default figmaKeys to NONE, correct the safe key grammar

Realigns the implementation with the revised ADR-066.

- Config.format.figmaKeys and ResolvedConfig gain NONE, and DEFAULT_CONFIG
  now supplies NONE rather than SENTENCE. The safe key grammar, the
  com.figma.name divergence extension, and reversal become opt-in; as
  previously implemented every catalog was opted in by default.
- workspace.schema.json: figmaKeys enum gains NONE and defaults to it.
- SafeKeySentence / SafeKeyTitle patterns corrected so a word is either all
  letters or all digits and a name is never digit-initial:
    ^[A-Z][a-z]*( ([a-z]+|[0-9]+))*$
    ^[A-Z][a-z]*( ([A-Z][a-z]*|[0-9]+))*$
  "Badge count 2" is now safe (it was rejected by the old pattern despite
  being listed as safe in the ADR); "Badge count2" is now unsafe, since both
  format to badgeCount2 and only the spaced form reverses correctly.
- name descriptions on FigmaAnatomyElementExtension and FigmaPropExtension
  now enumerate all triggers, including destination-format pass-through, and
  state that format divergence is not evaluated under NONE.
- ADR: align Decision 5 Option A with the idempotence predicate in the
  Decision body.

Producer work implied but not included here: the letter<->digit word-splitting
rule and the pass-through test are specs-from-figma behavior.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* docs(066): accept ADR, update docs site, repair ADR workflow scripts

- ADR-066 status DRAFT -> ACCEPTED; INDEX row moved to the Accepted table
  with highlights. All three gates pass: tsc build, schema validation (5/5),
  and strict type tests.
- Docs site brought in line with the accepted ADR. These were written against
  the original draft and still documented SENTENCE as the default and the
  superseded grammar:
  - settings/figma-keys.md: NONE added and defaulted, new "Opting in" section
    framing the fidelity/spec-size trade, pass-through behavior
  - guides/key-formatting.md: corrected grammar (a word is all letters or all
    digits, never digit-initial), digit-run splitting rule, destination-format
    pass-through, and a "What is not covered" section
  - schema/config.md: type union, default column, DEFAULT_CONFIG snippet
  - settings/index.mdx: sample config notes that SENTENCE opts in
- Repair two ADR workflow scripts broken by deb1325, which relocated them
  from .specify/scripts/bash/ to scripts/ without updating their path
  assumptions:
  - validate-schema.sh walked up three levels and resolved the schema dir
    outside the repo entirely; now resolves via git rev-parse and defaults to
    packages/schema/schema
  - check-prerequisites.sh sourced common.sh, deleted by the same commit; the
    helpers it used (repo root, branch, feature paths, branch validation) are
    inlined so it has no external dependency

overview/releases.mdx deliberately unchanged — its originalName references are
historical notes describing what shipped in 0.28.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* chore(adr): check for an existing PR before accepting

The accept agent assumed a fresh ADR branch and unconditionally ran
gh pr create at the end, so re-running it on a branch already in review
would open a duplicate PR.

- New step 2, before loading context or running gates: query
  gh pr list --head "$BRANCH" --state all and branch on the result.
  Open -> adopt its base and treat the run as an update; merged -> halt;
  closed-unmerged -> report and ask; none -> the original path.
- Step 3 no longer halts flatly when the ADR is already ACCEPTED. It halts
  only when there is also nothing uncommitted; otherwise it skips the status
  flip and continues so follow-up changes still reach the existing PR.
- Step 7 (release branch) is skipped when a PR exists — the PR's base is
  authoritative.
- Step 8 creates a PR only when there isn't one, and otherwise offers to
  refresh the existing body after asking. Long bodies go via --body-file;
  an inline heredoc breaks on backticks and quotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* feat(066): destination-format retention, NumberProp extensions, config template

Refines ADR-066 against the implementation in specs-from-figma.

- Decision 5 restated as three ordered tests: a name matching the origin
  convention formats and stays quiet; a name matching the destination
  convention (format.keys) is retained verbatim and recorded; a name matching
  neither is formatted and recorded. Only the destination retains — a PascalCase
  name with a KEBAB destination is reformatted like any divergent name, since
  formatting flattens within a convention rather than converting between them.
  The origin test runs first so a name satisfying both stays quiet.
- NumberProp gains $extensions. It was the only prop type without it, so a
  number prop had nowhere to record its Figma name. The JSON schema already
  allowed $-prefixed keys via patternProperties; the TypeScript type did not.
- specs.config.yaml template documents format.figmaKeys, commented out at its
  NONE default, matching how other defaulted settings appear.
- Docs and changelogs updated to the destination-only rule.

Verified against 69 components in specs-testing/workspaces/eg: output is
byte-identical to the SENTENCE baseline, 149 preserved names, no key changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* feat(cli): add the keys analyzer (ADR-066)

Reports Figma layer and property names that a formatted key cannot reconstruct,
so they can be tidied at the source.

Organized byComponent first — a designer edits one component at a time, so that
is the shape of the checklist. byCause follows, for deciding what to fix in
bulk, and byName last, because a name wrong in forty components is one decision
rather than forty.

Reads generated specs, so it sees a name only where the producer recorded it in
$extensions['com.figma'].name. That requires format.figmaKeys to declare a
convention; under the NONE default the report is empty, which is correct — no
convention was declared, so nothing diverged from one.

Causes are ordered most-specific first, since that is the edit to make.
'already-a-key' is last and is not a defect: it marks a name deliberately
authored in a key convention, which the producer retains as written.

Docs: new site page at cli/analyze/keys, added to the analyze overview table,
output tree, invocation examples, and sidebar.

Verified against 69 components in specs-testing/workspaces/eg — 948 names, 75
divergent across 52 components, with 'A11y label' appearing in 42 of them and a
'Label   ' with trailing spaces surfacing as a genuine authoring defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* refactor(cli): split keys byComponent by surface

byComponent entries no longer carry a `surface` field on a combined `names`
array. Each component now emits `props` and `anatomy` arrays, with the surface
implied by which array an entry is in:

  egdsMenu:
    divergent: 3
    props:
      - key: a11yLabel
        figmaName: A11y label
        cause: mixed-letter-digit
    anatomy:
      - key: egdsBottomSheet
        figmaName: EGDS Bottom Sheet
        cause: casing

A surface with nothing to fix is omitted rather than emitted as an empty array,
keeping the checklist quiet. `divergent` remains the total across both. summary,
byCause, and byName are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* docs: note the byComponent surface split in the cli changelog

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

* fix(cli): stop the keys analyzer reporting collapse provenance as a defect

$extensions['com.figma'].name has two independent triggers: format divergence
(ADR-066) and wrapper-collapse provenance (ADR-058). The analyzer treated the
presence of the field as proof a name had diverged, so it reported collapsed
roots as naming problems even when their names were perfectly well-formed —
'Text' on egdsBody/egdsHeading/egdsText and 'Icon glyph' on egdsIcon all satisfy
the SENTENCE grammar and reconstruct from their keys unaided.

causeOf then compounded it by returning 'casing' as a catch-all, labelling those
names a casing defect.

The analyzer now reads metadata.config.format.figmaKeys from the spec and
re-tests each recorded name against the safe key grammar, reporting only genuine
failures. 'casing' is now a verdict reached after every other rule has been ruled
out on a name already known to fail, not a fallback.

This also makes the code agree with the docs, which already stated the report is
empty under the NONE default — it was not, it listed the four collapsed roots.

eg catalog: 75 divergent across 52 components becomes 71 across 49, with casing
28 -> 24. Every other cause is unchanged, which is the check that only the false
positives went.

Grammar patterns are local literals mirroring SafeKeySentence / SafeKeyTitle:
importing the schema JSON did not survive esbuild bundling, and a runtime package
path lookup is the wrong dependency for a single-file CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
066 appeared in both tables after the merge: the accepted row from #296, plus
the draft row claimed in 8392fe7, which still carried the pre-acceptance title
("Original-Name Preservation"). Removes the draft row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y
Nine ADRs sat in both INDEX tables — 035, 041, 043, 051, 055, 057, 058, 059,
064 — each with a Status of ACCEPTED in its own file and an empty-Highlights
draft row left behind. Removed all nine; the index now has 21 draft and 45
accepted rows with no overlap.

The accept agent already said to "move" the row, which reads as one action and
was carried out as an add. Step 6 now splits it into delete, insert, and a grep
that must return exactly one match, with a note that a duplicate can also appear
when a draft row claimed on the release branch reconciles with an in-flight ADR
branch — which is how 066 ended up listed twice under two different titles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y
The index listed 21 drafts, most of which shipped some time ago. Each was
checked against three sources: its INDEX row, the Status in its own file, and
whether the change exists in the schema.

Flipped DRAFT -> ACCEPTED in the ADR file and moved the INDEX row (12), each
verified present in the schema:

  022 SlotProp.nullable            034 emptyVariants, variantNames removed
  042 Composition + SlotContent    046 InstanceExample + instanceExamples
  047 slotContentExamples          048 PropBinding in PropConfigurationValue
  049 SlotContentRef               050 defaultSlotContent + instanceExamples
  053 config.transformers          054 workspace.schema.json
  062 textOverflow + maxLines      065 NumberProp.nullable

Moved the INDEX row only, the file already reading ACCEPTED (3):

  052 $nested on PropConfigurations    056 minChildren/maxChildren
  061 concern-split entry points (confirmed shipped; ADR file lives on
      origin/061-concern-split-schemas, so its status is untouched here)

Added a Superseded table and moved 025 there, closed in favour of ADR-042
composition and ADR-047 slot content.

Still draft, confirmed not shipped: 020, 024, 044, 045, and 021 — the schema
and types both still carry invalidVariantCombinations, so the rename that ADR
describes has not happened.

The accept agent now documents the Superseded table alongside Draft and
Accepted, so a closed ADR gets a pointer to what replaced it rather than
lingering as unfinished work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TH28uXaeosYCb8om9Jru2y
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant